/**
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { DateObject, MonthTag, weekDays } from '../types/common';
import { useDate } from './use-date';
import { useMonth } from './use-month';
import { UseCalendar } from './types';
import { DayInCalendar, WeekInCalendar } from '../types/calendar';

export default function useCalendar(): UseCalendar {
    const { getToday, getDayNumber } = useDate();
    const { daysInMonth, daysInPreMonth, getNextMonth, getPreviousMonth } = useMonth();

    function getSundayIndex(firstDayOfWeek: string): number {
        // Index of Sunday day
        const dayIdx = weekDays.indexOf(firstDayOfWeek);
        return dayIdx > 0 ? 7 - dayIdx : 0;
    }

    function getPreviousDay(date: DateObject) {
        const { day, month, year } = date;
        if (day === undefined || month === undefined || year === undefined) {
            throw new Error('invalided date.');
        }

        const shouldGetDayInPreviousMonth = day === 1;
        const previousDay = shouldGetDayInPreviousMonth ? daysInPreMonth(month, year) : day - 1;

        const intendingMonthInPreviousDay = shouldGetDayInPreviousMonth ? month - 1 : month;

        const shouldGetMonthInPreviousYear = intendingMonthInPreviousDay < 1;

        const monthInPreviousDay = shouldGetMonthInPreviousYear ? 12 : intendingMonthInPreviousDay;

        const yearInPreviousDay = shouldGetMonthInPreviousYear ? year - 1 : year;

        return {
            year: yearInPreviousDay,
            month: monthInPreviousDay,
            day: previousDay
        } as DateObject;
    }

    function getNextDay(date: DateObject) {
        const { day, month, year } = date;
        if (day === undefined || month === undefined || year === undefined) {
            throw new Error('invalided date.');
        }

        const daysInCurrentMonth: number = daysInMonth(month, year);
        const shouldGetDayInNextMonth = day === daysInCurrentMonth;
        const nextDay = shouldGetDayInNextMonth ? 1 : day + 1;

        const intendingMonthInNextDay = shouldGetDayInNextMonth ? month + 1 : month;

        const shouldGetMonthInNextYear = intendingMonthInNextDay > 12;

        const monthInNextDay = shouldGetMonthInNextYear ? 1 : intendingMonthInNextDay;

        const yearInNextDay = shouldGetMonthInNextYear ? year + 1 : year;

        return {
            year: yearInNextDay,
            month: monthInNextDay,
            day: nextDay
        } as DateObject;
    }

    function getDayInPreviousMonth(date: DateObject): DateObject {
        const previousMonth = getPreviousMonth(date.month || 1, date.year || 1);
        const lastDayInPreviousMonth = daysInMonth(previousMonth.month || 1, previousMonth.year || 1);
        return {
            day: (date.day || 1) <= lastDayInPreviousMonth ? date.day : lastDayInPreviousMonth,
            month: previousMonth.month,
            year: previousMonth.year
        };
    }

    function getDayInNextMonth(date: DateObject): DateObject {
        const nextMonth = getNextMonth(date.month || 1, date.year || 1);
        const lastDayInNextMonth = daysInMonth(nextMonth.month || 1, nextMonth.year || 1);
        return {
            day: (date.day || 1) <= lastDayInNextMonth ? date.day : lastDayInNextMonth,
            month: nextMonth.month,
            year: nextMonth.year
        };
    }

    function getDayInPreviousWeek(date: DateObject): DateObject {
        const day = date.day || 1;
        const intendingPreviousDay = day - 7;
        const shouldGetPreviousMonth = intendingPreviousDay < 1;
        const previousDay = shouldGetPreviousMonth
            ? daysInMonth(date.month || 1, date.year || 1) + intendingPreviousDay
            : intendingPreviousDay;
        const previousDayMonth = shouldGetPreviousMonth ? getPreviousMonth(date.month || 1, date.year || 1) : date;
        return {
            day: previousDay,
            month: previousDayMonth.month,
            year: previousDayMonth.year
        };
    }

    function getDayInNextWeek(date: DateObject): DateObject {
        const day = date.day || 1;
        const intendingNextDay = day + 7;
        const daysInCurrentMonth = daysInMonth(date.month || 1, date.year || 1);
        const shouldGetNextMonth = intendingNextDay > daysInCurrentMonth;
        const nextDay = shouldGetNextMonth ? intendingNextDay - daysInCurrentMonth : intendingNextDay;
        const nextDayMonth = shouldGetNextMonth ? getNextMonth(date.month || 1, date.year || 1) : date;
        return {
            day: nextDay,
            month: nextDayMonth.month,
            year: nextDayMonth.year
        };
    }

    function getWeeklyCalendar(day: number, month: number, year: number, firstDayOfWeek: string): WeekInCalendar {
        const date = { day, month, year };
        const sundayIndex = getSundayIndex(firstDayOfWeek);
        const offset = sundayIndex === 0 ? 1 : 0;
        const currentDayIndexInWeek = getDayNumber(date) + offset;
        const today: DateObject = getToday();

        let previousDay: DateObject = getPreviousDay(date);
        const previousDays: DayInCalendar[] = [];
        for (let dayIndexInWeek = currentDayIndexInWeek - 1; dayIndexInWeek >= 1; dayIndexInWeek--) {
            const monthTag = previousDay.month !== date.month ? MonthTag.previous : MonthTag.current;
            const isCurrent =
                previousDay.month === month &&
                previousDay.day === today.day &&
                previousDay.month === today.month &&
                previousDay.year === today.year;
            previousDays.push({ date: previousDay, monthTag, isCurrent });
            if (dayIndexInWeek > 1) {
                previousDay = getPreviousDay(previousDay);
            }
        }

        let nextDay: DateObject = getNextDay(date);
        const nextDays: DayInCalendar[] = [];
        for (let dayIndexInWeek = currentDayIndexInWeek + 1; dayIndexInWeek <= 7; dayIndexInWeek++) {
            const monthTag = previousDay.month !== date.month ? MonthTag.next : MonthTag.current;
            const isCurrent = nextDay.day === today.day && nextDay.month === today.month && nextDay.year === today.year;
            nextDays.push({ date: nextDay, monthTag, isCurrent });
            if (dayIndexInWeek < 7) {
                nextDay = getNextDay(nextDay);
            }
        }

        const isCurrent = day === today.day && month === today.month && year === today.year;
        const weekCalendar: WeekInCalendar = {
            days: [...previousDays.reverse(), { date, monthTag: MonthTag.current, isCurrent }, ...nextDays],
            weekNumber: 0,
            year
        };
        return weekCalendar;
    }

    function getMonthlyCalendar(month: number, year: number, firstDayOfWeek: string): WeekInCalendar[] {
        const weeks: WeekInCalendar[] = [];
        const daysInCurrentMonth: number = daysInMonth(month, year);
        for (let day = 1; day <= daysInCurrentMonth; day += 7) {
            const week = getWeeklyCalendar(day, month, year, firstDayOfWeek);
            weeks.push(week);
        }
        return weeks;
    }

    return {
        getMonthlyCalendar,
        getWeeklyCalendar,
        getPreviousDay,
        getNextDay,
        getDayInPreviousWeek,
        getDayInNextWeek,
        getDayInPreviousMonth,
        getDayInNextMonth
    };
}
